﻿namespace Kodefu
{
    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.IO;
    using System.Linq;

    /// <summary>
    /// Extension methods for <see cref="String"/>. 
    /// </summary>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static class StringExtensions
    {
        /// <summary>
        /// Creates a new instance of <see cref="String"/> with the same value as a specified <see cref="String"/>.
        /// </summary>
        /// <param name="source">The <see cref="String"/> to copy.</param>
        /// <returns>A new <see cref="String"/> with the same value as str.</returns>
        public static string Copy(this string source)
        {
            return String.Copy(source);
        }

        /// <summary>
        /// Replaces the format item in the source <see cref="String"/> with the text equivalent of the value of a corresponding <see cref="Object"/> instance in a specified array.
        /// </summary>
        /// <param name="format">A <see cref="String"/> containing zero or more format items.</param>
        /// <param name="args">An <see cref="Object"/> array containing zero or more objects to format.</param>
        /// <returns>A copy of the source string in which the format items have been replaced by the <see cref="String"/> equivalent of the corresponding instances of <see cref="Object"/> in args.</returns>
        public static string FormatWith(this string format, params object[] args)
        {
            return format.FormatWith(CultureInfo.InvariantCulture, args);
        }

        /// <summary>
        /// Replaces the format item in the source <see cref="String"/> with the text equivalent of the value of a corresponding <see cref="Object"/> instance in a specified array.
        /// </summary>
        /// <param name="format">A <see cref="String"/> containing zero or more format items.</param>
        /// <param name="provider">An <see cref="IFormatProvider"/> that supplies culture-specific formatting information. </param>
        /// <param name="args">An <see cref="Object"/> array containing zero or more objects to format.</param>
        /// <returns>A copy of the source string in which the format items have been replaced by the <see cref="String"/> equivalent of the corresponding instances of <see cref="Object"/> in args.</returns>
        public static string FormatWith(this string format, IFormatProvider provider, params object[] args)
        {
            return String.Format(provider, format, args);
        }

        /// <summary>
        /// Retrieves the system's reference to the specified <see cref="String"/>.
        /// </summary>
        /// <param name="source">A <see cref="String"/> check in the intern pool.</param>
        /// <returns>If the value of str is already interned, the system's reference is returned; 
        /// otherwise, a new reference to a string with the value of str is returned.</returns>
        public static string Intern(this string source)
        {
            return String.Intern(source);
        }

        /// <summary>
        /// Indicates whether the specified <see cref="String"/> object is null or an <see cref="String.Empty"/> string.
        /// </summary>
        /// <param name="source">A <see cref="String"/> reference.</param>
        /// <returns>true if the <see cref="String"/> is null or an empty string (""); otherwise, false.</returns>
        public static bool IsNullOrEmpty(this string source)
        {
            return String.IsNullOrEmpty(source);
        }

        public static void Write(this string source, string fileName)
        {
            File.WriteAllText(fileName, source);
        }

        /// <summary>
        /// <para>Calculates the Levenshtein distance from one string to another.</para>
        /// <para>The Levenshtein distance between two strings is defined as the minimum number of edits needed to transform one string into the other, 
        /// with the allowable edit operations being insertion, deletion, or substitution of a single character. 
        /// It is named after Vladimir Levenshtein, who considered this distance in 1965.</para>
        /// </summary>
        /// <param name="str1"></param>
        /// <param name="str2"></param>
        /// <returns>the Levenshtein distance</returns>
        /// <remarks>This should be refactored to calculate the distance between any sequence.</remarks>
        public static int DistanceTo(this string str1, string str2)
        {
            var matrix = new int[str1.Length + 1, str2.Length + 1];

            for (var i = 0; i <= str1.Length; i++)
                matrix[i, 0] = i;
            for (var j = 0; j <= str2.Length; j++)
                matrix[0, j] = j;

            for (var i = 1; i <= str1.Length; i++)
            {
                for (var j = 1; j <= str2.Length; j++)
                {
                    var cost = str1[i - 1] == str2[j - 1] ? 0 : 1;

                    matrix[i, j] = (new[]
            {
                matrix[i - 1, j] + 1, matrix[i, j - 1] + 1, matrix[i - 1, j - 1] + cost}).Min();

                    if ((i > 1) &&
                        (j > 1) &&
                        (str1[i - 1] == str2[j - 2]) &&
                        (str1[i - 2] == str2[j - 1]))
                    {
                        matrix[i, j] = Math.Min(matrix[i, j], matrix[i - 2, j - 2] + cost);
                    }
                }
            }

            return matrix[str1.Length, str2.Length];
        }
    }
}
